/**
 * Flogger - Logging Console for ActionScript
 * Copyright (c) 2010-2011 Ariel Flesler | aflesler(at)gmail(dot)com
 * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
 * Date: 3/28/2011
 *
 * @author Ariel Flesler
 * @version 1.1.1.0
 */

package {
	
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.AsyncErrorEvent;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.StatusEvent;
	import flash.net.LocalConnection;
	import flash.net.SharedObject;
	import flash.system.System;
	import flash.system.fscommand;
	import flash.text.TextField;
	import flash.text.TextFieldType;
	import flash.text.TextFormat;
	import flash.ui.Keyboard;
	import flash.utils.setTimeout;

	
	// TODO: Implement filters by log level
	[SWF(width='800', height='200', backgroundColor="0x000000", frameRate='25')]
	public class flogger extends Sprite
	{
		// Useful configs
		private static const CONNECTION_IN_NAME:String = '_flogger_log';
		private static const CONNECTION_OUT_NAME:String = '_flogger_cmd';
		
		// Colors
		private static const SYSTEM_COLOR:uint = 0xBB77BB;
		private static const DEBUG_COLOR:uint = 0xEEEEEE;
		private static const INFO_COLOR:uint = 0x55C9D3;
		private static const WARN_COLOR:uint = 0xFFFF00;
		private static const ERROR_COLOR:uint = 0xFF0000;
		private static const FATAL_COLOR:uint = 0xBB0055;
		
		private static const CONNECT_DELAY:uint = 1e3;
		private static const RECONNECT_DELAY:uint = 5e3;
		private static const COMMAND_HANDLER:String = 'command';
		
		// Textfields
		private static const SCALE_DELTA:Number = 0.1;
		private static const INPUT_HEIGHT:uint = 19;
		private static const ERROR_STATUS:String = 'error';
		
		private var inConnection:LocalConnection;
		private var outConnection:LocalConnection;
		
		private var console:TextField;
		private var scrollPos:ScrollPos;
		
		private var input:TextField;
		private var commandHistory:Array;
		private var historyIndex:int;
		
		private var so:SharedObject;
		
		public function flogger()
		{
			init();
		}
		
		private function init():void
		{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
			
			so = SharedObject.getLocal('flogger');
			scrollPos = new ScrollPos();
			
			console = new TextField();
			if (so.data.consoleScale)
				updateScale(so.data.consoleScale);
			console.addEventListener(MouseEvent.CLICK, copyToClipboard);
			console.addEventListener(Event.SCROLL, updateScrollPos);
			addChild(console);
			
			CONFIG::ENABLE_SEND {
				initInput();
			}
			
			stage.addEventListener(Event.RESIZE, updateOnResize);
			updateOnResize();
			addChild(scrollPos);

			inConnection = createConnection();
			inConnection.client = this;
			
			killPrevious();
			setTimeout(tryToConnect, CONNECT_DELAY);
			
			stage.addEventListener(KeyboardEvent.KEY_DOWN, checkKeys);
			stage.addEventListener(MouseEvent.MOUSE_WHEEL, changeSize, true);

			stage.stageFocusRect = true;
			stage.focus = console;
		}
		
		private function ignore(e:Event):void
		{
		}
		
		private function createConnection():LocalConnection
		{
			var connection:LocalConnection = new LocalConnection();
			connection.allowDomain('*');
			connection.allowInsecureDomain('*');
			connection.addEventListener(AsyncErrorEvent.ASYNC_ERROR, ignore);
			connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, ignore);
			connection.addEventListener(StatusEvent.STATUS, ignore);
			return connection;
		}
		
		private function initInput():void
		{
			input = new TextField();
			input.type = TextFieldType.INPUT;
			input.borderColor = input.textColor = 0xFFFFFF;
			input.border = true;
			input.height = INPUT_HEIGHT;
			addChild(input);
			
			commandHistory = [];
			
			outConnection = createConnection();
			outConnection.addEventListener(StatusEvent.STATUS, checkSentStatus);
		}
		
		private function checkSentStatus(e:StatusEvent):void
		{
			if (e.level === ERROR_STATUS)
				warn('Command was not received');
		}
		
		private function updateOnResize(e:Event=null):void 
		{
			console.width = scrollPos.x = stage.stageWidth;
			CONFIG::ENABLE_SEND {
				input.width = console.width - 1;
				input.y = stage.stageHeight - INPUT_HEIGHT - 1;
			}
			updateConsoleHeight();
			updateScrollPos();
		}
		
		private function updateScrollPos(e:Event=null):void
		{
			var pos:int = scroll === 1 ? 0 : scroll;
			scrollPos.y = (pos / console.maxScrollV) * (consoleHeight - scrollPos.height);
		}
		
		private function changeSize(e:MouseEvent):void
		{
			if (e.ctrlKey)
				changeScale(e.delta > 0 ? 1 : -1);
		}
		
		private function changeScale(dir:int):void
		{
			var scale:Number = console.scaleX + dir * SCALE_DELTA;
			updateScale(scale);
			so.data.consoleScale = scale;
			so.flush();
		}
		
		private function updateScale(scale:Number):void
		{
			console.scaleY = console.scaleX = scale;
			updateConsoleHeight();
			updateScrollPos();
		}
		
		private function get scroll():int
		{
			return console.scrollV;
		}
		
		private function set scroll(v:int):void
		{
			console.scrollV = v;
		}
		
		private function copyToClipboard(e:MouseEvent):void
		{
			System.setClipboard(console.text);
		}
		
		private function get consoleHeight():int
		{
			return stage.stageHeight - (CONFIG::ENABLE_SEND ? INPUT_HEIGHT+1 : 0);
		}
		
		private function updateConsoleHeight():void
		{
			console.height = consoleHeight / console.scaleY;
		}
		
		private function checkKeys(e:KeyboardEvent):void
		{
			switch (e.keyCode) {
				case Keyboard.ESCAPE		 : clear(); break;
				case Keyboard.NUMPAD_ADD	 : changeScale(1); break;
				case Keyboard.NUMPAD_SUBTRACT: changeScale(-1); break;
			}
			
			CONFIG::ENABLE_SEND {
				switch (e.keyCode) {
					case Keyboard.ENTER: submit(); break;
					case Keyboard.UP: changeCommand(-1); break;
					case Keyboard.DOWN: changeCommand(1); break;
					default: stage.focus = input; break;
				}
			}
		}
		
		private function submit():void
		{
			var code:String = input.text;
			if (commandHistory.indexOf(code) !== -1)
				commandHistory.splice(commandHistory.indexOf(code), 1);
			historyIndex = commandHistory.push(code);
			system('> '+code);
			outConnection.send(CONNECTION_OUT_NAME, COMMAND_HANDLER, code);
			input.text = '';
		}
		
		private function changeCommand(delta:int):void
		{
			var index:int = historyIndex + delta;
			var max:int = commandHistory.length;
			if (index < 0 || index > max) 
				return;
			
			if (index === max) {
				input.text = '';
			} else {
				input.text = commandHistory[index];
			}
			historyIndex = index;
			setTimeout(focusInputEnd, 10);
		}
		
		private function focusInputEnd():void
		{
			input.setSelection(input.length, input.length);
		}
		
		private function killPrevious():void
		{
			inConnection.send(CONNECTION_IN_NAME, 'quit');
		}
		
		private function tryToConnect():void
		{
			try	{
				inConnection.connect(CONNECTION_IN_NAME);
				system('Connected');
			}
			catch (err:Error) {
				error('Failed to connect! '+ err.message);
				setTimeout(tryToConnect, RECONNECT_DELAY);
			}
		}
		
		private function appendLine(color:uint, msg:String):void
		{
			var prevScroll:int = scroll;
			var wasAtEnd:Boolean = prevScroll === console.maxScrollV;
			
			var start:int = console.length;
			if (start)
				console.appendText('\n');
			console.appendText(msg);
			
			scroll = wasAtEnd ? console.maxScrollV : prevScroll;
			
			console.setTextFormat(
				new TextFormat('Verdana', 10, color),
				start, console.length
			);
		}
		
		private function system(msg:String):void
		{
			appendLine(SYSTEM_COLOR, msg);
		}
		
		// Exposed API
		
		/** Adds a log with debug level */
		public function debug(msg:String):void
		{
			appendLine(DEBUG_COLOR, msg);
		}
		
		/** Adds a log with info level */
		public function info(msg:String):void
		{
			appendLine(INFO_COLOR, msg);
		}
		
		/** Adds a log with warning level */
		public function warn(msg:String):void
		{
			appendLine(WARN_COLOR, msg);
		}
		
		/** Adds a log with error level */
		public function error(msg:String):void
		{
			appendLine(ERROR_COLOR, msg);
		}
		/** Adds a log with fatal level */
		public function fatal(msg:String):void
		{
			appendLine(FATAL_COLOR, msg);
		}
		
		
		/** Clears the console */
		public function clear():void
		{
			console.text = '';
		}
		
		/** Closes the console */
		public function quit():void
		{
			debug('Closing...');
			
			try	{ inConnection.close(); }
			catch (err:Error) {}
			
			inConnection.client = {};
			inConnection = null;

			fscommand('quit');
		}
	}
}
	import flash.display.Shape;
	

internal class ScrollPos extends Shape
{
	private static const SIZE:uint = 10;
	
	public function ScrollPos()
	{
		graphics.beginFill(0xBBBBBB);
		graphics.drawRect(-SIZE, 0, SIZE, SIZE);
		graphics.endFill();
	}
}